package cc.shacocloud.kotlin.tools.cli

import cc.shacocloud.kotlin.tools.Slf4j
import cc.shacocloud.kotlin.tools.Slf4j.Companion.log
import cc.shacocloud.kotlin.tools.cli.Utils.homeDir
import org.apache.maven.repository.internal.MavenRepositorySystemUtils
import org.eclipse.aether.DefaultRepositorySystemSession
import org.eclipse.aether.RepositorySystem
import org.eclipse.aether.artifact.Artifact
import org.eclipse.aether.artifact.DefaultArtifact
import org.eclipse.aether.collection.CollectRequest
import org.eclipse.aether.collection.DependencyCollectionException
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory
import org.eclipse.aether.graph.Dependency
import org.eclipse.aether.graph.Exclusion
import org.eclipse.aether.impl.DefaultServiceLocator
import org.eclipse.aether.repository.*
import org.eclipse.aether.resolution.DependencyRequest
import org.eclipse.aether.resolution.DependencyResolutionException
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory
import org.eclipse.aether.spi.connector.transport.TransporterFactory
import org.eclipse.aether.transport.file.FileTransporterFactory
import org.eclipse.aether.transport.http.HttpTransporterFactory
import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator
import java.io.File
import java.nio.file.Paths
import kotlin.io.path.absolutePathString

/**
 * maven 存储库
 *
 * @author 思追(shaco)
 */
@Slf4j
open class MavenRepository(

    /**
     * maven 依赖存储路径
     */
    saveBasedir: String

) {

    companion object : MavenRepository(Paths.get(homeDir, "repository").absolutePathString()) {

        /**
         * Windows、Linux 或 Mac 文件名中禁止使用这些字符。由于存储库 ID 用于元数据文件名生成，
         * 因此应将它们替换为允许的字符
         */
        private val FORBIDDEN_CHARS = "[/\\\\:<>\"|?*]".toRegex()

        /**
         * 依赖正则
         */
        val ARTIFACTS_PATTERN = "([^: ]+):([^: ]+)(:([^: ]*)(:([^: ]+))?)?".toPattern()

        private val releaseRepositoryPolicy = RepositoryPolicy(
            true,
            RepositoryPolicy.UPDATE_POLICY_NEVER,
            RepositoryPolicy.CHECKSUM_POLICY_WARN
        )

        private val snapshotRepositoryPolicy = RepositoryPolicy(
            true,
            RepositoryPolicy.UPDATE_POLICY_ALWAYS,
            RepositoryPolicy.CHECKSUM_POLICY_WARN
        )

        /**
         * 默认的远端存储库列表
         */
        val defaultRemoteRepository = listOf(

            // 阿里云存储库
            RemoteRepository.Builder("resp1", "default", "https://maven.aliyun.com/repository/public")
                .setReleasePolicy(releaseRepositoryPolicy)
                .setSnapshotPolicy(snapshotRepositoryPolicy)
                .build(),

            // maven 官方仓库
            RemoteRepository.Builder("resp2", "default", "https://repo1.maven.org/maven2")
                .setReleasePolicy(releaseRepositoryPolicy)
                .setSnapshotPolicy(snapshotRepositoryPolicy)
                .build()
        )

        /**
         * 转为远端存储库
         */
        fun toRemoteRepository(
            url: String,
            authentication: Authentication? = null,
            type: String = "default",
            id: String = url.replace(FORBIDDEN_CHARS, "_")
        ): RemoteRepository {
            val builder = RemoteRepository.Builder(id, type, url)
                .setReleasePolicy(releaseRepositoryPolicy)
                .setSnapshotPolicy(snapshotRepositoryPolicy)

            if (authentication != null) {
                builder.setAuthentication(authentication)
            }

            return builder.build()
        }

        /**
         * 是否为正确的依赖格式
         */
        fun isArtifacts(artifacts: String): Boolean {
            return ARTIFACTS_PATTERN.matcher(artifacts).matches()
        }

        /**
         * 如果依赖格式不正常则抛出例外
         */
        fun requireArtifacts(artifacts: String) {
            require(isArtifacts(artifacts)) {
                "依赖 $artifacts 格式错误, 预期的格式是 <groupId>:<artifactId>[:<extension>[:<classifier>]]"
            }
        }

    }

    private val localRepositoryManager: LocalRepositoryManager
    private val repositorySystem: RepositorySystem
    private val repositorySystemSession: DefaultRepositorySystemSession

    /**
     * 初始化一个maven集成工具
     */
    init {
        val repositorySystem: RepositorySystem = newRepositorySystem()

        // 初始化一个会话
        val session = MavenRepositorySystemUtils.newSession()

        val localRepo = LocalRepository(saveBasedir)
        val localRepositoryManager = repositorySystem.newLocalRepositoryManager(session, localRepo)

        session.setLocalRepositoryManager(localRepositoryManager)

        this.localRepositoryManager = localRepositoryManager
        this.repositorySystem = repositorySystem
        this.repositorySystemSession = session
    }

    /**
     * 判断指定工件是否存在
     *
     * 注意：这边只判断当前这个工件是否存在，不包含其相关的依赖工件
     *
     * @param artifacts 工件坐标，格式：<groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>
     * @return 如果存在则返回true，反之为false
     */
    fun exists(artifacts: String): Boolean {
        requireArtifacts(artifacts)

        val localArtifact = localRepositoryManager.getPathForLocalArtifact(DefaultArtifact(artifacts))
        val basedir = localRepositoryManager.repository.basedir
        return File(basedir, localArtifact).isFile
    }

    /**
     * 下载指定 [Artifact]
     *
     * @param artifacts              工件坐标，格式：<groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>
     * @param exclusionArtifactCoordsList 排除的工件坐标集，格式：<groupId>:<artifactId>[:<extension>[:<classifier>]]
     * @see download
     */
    @Throws(DependencyCollectionException::class, DependencyResolutionException::class)
    fun download(
        artifacts: String,
        exclusionArtifactCoordsList: Array<String> = emptyArray<String>(),
        repositoryList: List<RemoteRepository> = defaultRemoteRepository,
        warningsCallback: (Exception) -> Unit = {}
    ): List<File> {
        requireArtifacts(artifacts)

        if (log.isInfoEnabled) {
            if (repositoryList.isNotEmpty()) {
                log.info("开始下载远端依赖：${artifacts}，使用存储库：${repositoryList.joinToString { it.toString() }}")
            } else {
                log.info("开始下载远端依赖：${artifacts}")
            }
        }

        var dependency = Dependency(DefaultArtifact(artifacts), "compile")

        // 如果存在排除依赖
        if (exclusionArtifactCoordsList.isNotEmpty()) {
            val exclusions = exclusionArtifactCoordsList.map { coords ->
                val matcher = ARTIFACTS_PATTERN.matcher(coords)

                require(matcher.matches()) {
                    "排除依赖 $coords 格式错误,预期的格式是 <groupId>:<artifactId>[:<extension>[:<classifier>]]"
                }

                val groupId = matcher.group(1)
                val artifactId = matcher.group(2)
                val extension: String = matcher.group(4).orEmpty().ifEmpty { "jar" }
                val classifier: String = matcher.group(6).orEmpty().ifEmpty { "" }
                Exclusion(groupId, artifactId, classifier, extension)
            }

            dependency = dependency.setExclusions(exclusions)
        }

        // 构建请求
        val collectRequest = CollectRequest()
        collectRequest.setRoot(dependency)
        repositoryList.forEach { collectRequest.addRepository(it) }

        val collectDependencies = repositorySystem.collectDependencies(repositorySystemSession, collectRequest)
        collectDependencies.exceptions.forEach { warningsCallback(it) }

        val node = collectDependencies.root

        val dependencyRequest = DependencyRequest()
        dependencyRequest.setRoot(node)

        val resolveDependencies = repositorySystem.resolveDependencies(repositorySystemSession, dependencyRequest)
        resolveDependencies.artifactResults.flatMap { it.exceptions }.forEach { warningsCallback(it) }

        // 依赖节点生成器
        val nlg = PreorderNodeListGenerator()
        node.accept(nlg)

        val files = nlg.files

        if (log.isInfoEnabled) {
            log.info("远端依赖 [${artifacts}] 下载完成，共计：{} 个依赖文件", files.size)
        }

        return files
    }

    /**
     * 构建存储系统 [RepositorySystem]
     */
    private fun newRepositorySystem(): RepositorySystem {
        val locator: DefaultServiceLocator = MavenRepositorySystemUtils.newServiceLocator()
        locator.addService(
            RepositoryConnectorFactory::class.java,
            BasicRepositoryConnectorFactory::class.java
        )
        locator.addService(TransporterFactory::class.java, FileTransporterFactory::class.java)
        locator.addService(TransporterFactory::class.java, HttpTransporterFactory::class.java)
        return locator.getService(RepositorySystem::class.java)
    }

}
